﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using WcfContrib.Helpers;
using WcfContrib.Utils;
using WcfContrib.Resources;
using System.Net;

namespace WcfContrib.Hosting
{
    /// <summary>
    /// Represents a service configuration description for creating service hosts.
    /// </summary>
	public class ServiceConfigurationDescription
	{
		#region Fields
        static readonly KnownEndpointConfiguration[] DesiredConfigurations = new[] { 
            KnownEndpointConfiguration.NetNamedPipe, KnownEndpointConfiguration.NetTcpOneWay, KnownEndpointConfiguration.NetTcp, KnownEndpointConfiguration.NetTcpReliableSession, KnownEndpointConfiguration.WsHttp, KnownEndpointConfiguration.HttpBinary, KnownEndpointConfiguration.BasicHttp, KnownEndpointConfiguration.WebHttp, KnownEndpointConfiguration.WebHttpScriptEnabled, KnownEndpointConfiguration.NetMsmq };

        //This should hold the aggregation to the host addres to make it a FQDN
		const string FullMachineNameAggregate = "";
		const string MexUriAggregate = "/mex";
		const string LocalMachineAddress = "localhost";
		const KnownSecurityMode DefaultSecurityMode = KnownSecurityMode.Windows;
		#endregion

		#region Properties
        /// <summary>
        /// Gets or sets the machine address.
        /// </summary>
        /// <value>The machine address.</value>
		public string MachineAddress { get; set; }

        /// <summary>
        /// Gets or sets the send timeout.
        /// </summary>
        /// <value>The send timeout.</value>
		public TimeSpan SendTimeout { get; set; }
        /// <summary>
        /// Gets or sets the receive timeout.
        /// </summary>
        /// <value>The receive timeout.</value>
		public TimeSpan ReceiveTimeout { get; set; }
        /// <summary>
        /// Gets or sets the open timeout.
        /// </summary>
        /// <value>The open timeout.</value>
		public TimeSpan OpenTimeout { get; set; }
        /// <summary>
        /// Gets or sets the close timeout.
        /// </summary>
        /// <value>The close timeout.</value>
		public TimeSpan CloseTimeout { get; set; }
        /// <summary>
        /// Gets or sets the inactivity timeout.
        /// </summary>
        /// <value>The inactivity timeout.</value>
        public TimeSpan InactivityTimeout { get; set; }

        /// <summary>
        /// Gets or sets the service address.
        /// </summary>
        /// <value>The service address.</value>
		public string ServiceAddress { get; set; }
        /// <summary>
        /// Gets or sets the binding namespace.
        /// </summary>
        /// <value>The binding namespace.</value>
		public string BindingNamespace { get; set; }

        /// <summary>
        /// Gets or sets the base addresses.
        /// </summary>
        /// <value>The base addresses.</value>
		public Uri[] BaseAddresses { get; set; }

        /// <summary>
        /// Gets or sets the metadata options.
        /// </summary>
        /// <value>The metadata options.</value>
		public MetadataOptions Metadata { get; set; }

        /// <summary>
        /// Gets or sets the protocols descriptions.
        /// </summary>
        /// <value>The protocols descriptions.</value>
		public List<ProtocolConfigurationDescription> Protocols { get; set; }
        /// <summary>
        /// Gets or sets the endpoints descriptions.
        /// </summary>
        /// <value>The endpoints descriptions.</value>
		public List<EndpointConfigurationDescription> Endpoints { get; set; }

		int endpointCount = -1;
		int EndpointCount
		{
			get
			{
				if (endpointCount < 0)
				{
					endpointCount = Endpoints.Count;

					if (HasMetadataOption(MetadataOptions.MetadataExchangeHttp))
					{
						endpointCount++;
					}
					if (HasMetadataOption(MetadataOptions.MetadataExchangeTcp))
					{
						endpointCount++;
					}
				}

				return endpointCount;
			}
		}
		#endregion

		#region Instancing
        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceConfigurationDescription"/> class.
        /// </summary>
		public ServiceConfigurationDescription()
		{
			Protocols = new List<ProtocolConfigurationDescription>();
			Endpoints = new List<EndpointConfigurationDescription>();
			Metadata = MetadataOptions.None;

			SendTimeout = ReceiveTimeout = OpenTimeout = CloseTimeout = TimeSpan.FromMinutes(1);
            InactivityTimeout = TimeSpan.FromMinutes(10);
		}
		#endregion

        #region Load
        internal void GenerateEndpoints(ServiceHost host)
        {
            ServiceEndpoint firstEndpoint;
            GenerateEndpoints(host, Endpoints, null, false, out firstEndpoint);
        }

        void GenerateEndpoints(ServiceHost host, IEnumerable<EndpointConfigurationDescription> desiredEndpoints, IEnumerable<Type> contractsQuery, bool propagateContractToUri, out ServiceEndpoint firstEndpoint)
        {
            firstEndpoint = null;

            bool searchInBaseAddresses = BaseAddresses != null && BaseAddresses.Length > 0;
            bool setBindingNamespace = !string.IsNullOrEmpty(BindingNamespace);

            if (contractsQuery == null)
            {
                contractsQuery = from t in host.Description.ServiceType.GetInterfaces()
                                 where t.GetCustomAttributes(typeof(ServiceContractAttribute), false).Length > 0
                                 select t;
            }

            Type[] interfaceTypes = contractsQuery.Where(t => contractsQuery.Where(ct => t != ct && t.IsAssignableFrom(ct)).FirstOrDefault() == null).ToArray();

            bool addContractToUri = interfaceTypes.Length > 1;

            List<Type> addedContractTypes = new List<Type>();
            foreach (var contractType in contractsQuery)
            {
                if (addedContractTypes.Where(t => contractType.IsAssignableFrom(t)).FirstOrDefault() == null)
                {
                    addedContractTypes.Add(contractType);
                }
            }

            foreach (var endpointConfig in desiredEndpoints)
            {
                bool hasContractTypes = endpointConfig.ContractTypes != null && endpointConfig.ContractTypes.Length > 0;

                IEnumerable<Type> contractTypes = hasContractTypes
                    ? endpointConfig.ContractTypes
                    : (IEnumerable<Type>)addedContractTypes;

                foreach (var contractType in contractTypes)
                {
                    Type contractUriType = propagateContractToUri ? contractType : null;

                    if (hasContractTypes
                        && Array.IndexOf(endpointConfig.ContractTypes, contractType) < 0)
                    {
                        continue;
                    }

                    ProtocolConfigurationDescription protocolConfig = GetProtocolDescription(endpointConfig.Protocol);

                    //set contract suffix if contract uri was requested
                    //and the endpoint actually implements more than one contract
                    string addressSuffix =
                        addContractToUri && hasContractTypes && endpointConfig.ContractTypes.Length > 1
                        ? contractType.Name
                        : null;

                    Uri addressUri = GetServiceAddress(contractUriType, endpointConfig.Port, protocolConfig,
                        searchInBaseAddresses, addressSuffix, endpointConfig.AddressSuffix);

                    IEndpointBehavior[] applyBehaviors;
                    Binding binding = GetBinding(endpointConfig, out applyBehaviors);

                    ServiceEndpoint ep = (host != null)
                        ? host.AddServiceEndpoint(contractType, binding, addressUri)
                        : new ServiceEndpoint(ContractDescription.GetContract(contractType), binding, new EndpointAddress(addressUri));

                    if (applyBehaviors != null)
                    {
                        foreach (var behavior in applyBehaviors)
                        {
                            ep.Behaviors.Add(behavior);
                        }
                    }

                    if (host == null)
                    {
                        firstEndpoint = ep;
                        return;
                    }
                    else
                    {
                        ep.Binding.Namespace = setBindingNamespace
                            ? BindingNamespace
                            : host.Description.Namespace;
                    }
                }
            }

            if (!HasMetadataOption(MetadataOptions.None))
            {
                ServiceMetadataBehavior metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
                if (metadataBehavior == null)
                {
                    metadataBehavior = new ServiceMetadataBehavior();
                    host.Description.Behaviors.Add(metadataBehavior);
                }

                if (HasMetadataOption(MetadataOptions.MetadataExchangeHttp))
                {
                    ProtocolConfigurationDescription protocolConfig = GetProtocolDescription(Protocol.Http);
                    Uri addressUri = GetServiceAddress(null, null, protocolConfig, searchInBaseAddresses);
                    string address = EndpointHelper.CombineAddresses(addressUri.AbsoluteUri, MexUriAggregate);

                    host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                        MetadataExchangeBindings.CreateMexHttpBinding(), address);
                }
                if (HasMetadataOption(MetadataOptions.MetadataExchangeTcp))
                {
                    ProtocolConfigurationDescription protocolConfig = GetProtocolDescription(Protocol.NetTcp);
                    Uri addressUri = GetServiceAddress(null, null, protocolConfig, searchInBaseAddresses);
                    string address = EndpointHelper.CombineAddresses(addressUri.AbsoluteUri, MexUriAggregate);

                    host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName,
                        MetadataExchangeBindings.CreateMexTcpBinding(), address);
                }
                if (HasMetadataOption(MetadataOptions.HttpGet))
                {
                    ProtocolConfigurationDescription protocolConfig = GetProtocolDescription(Protocol.Http);
                    Uri addressUri = GetServiceAddress(null, null, protocolConfig, searchInBaseAddresses);

                    metadataBehavior.HttpGetEnabled = true;
                    metadataBehavior.HttpGetUrl = addressUri;
                }
            }
        }

        internal ChannelFactory GenerateChannelFactory(ServiceHost host, Type contractType, KnownEndpointConfiguration? discoveryEndpoint)
        {
            if (Endpoints == null || Endpoints.Count == 0 || (host != null && host.Description.Endpoints.Count == 0))
            {
                throw new InvalidOperationException(ExceptionMessages.HostingConfig_NoContext);
            }

            ServiceEndpoint ep = null;
            IEnumerable<KnownEndpointConfiguration> configurations = discoveryEndpoint.HasValue
                ? new[] { discoveryEndpoint.Value }
                : DesiredConfigurations;

            foreach (var knownEp in configurations)
            {
                EndpointConfigurationDescription epConfig =
                    Endpoints.Where(e => e.KnownConfiguration == knownEp).FirstOrDefault();

                if (epConfig != null)
                {
                    GenerateEndpoints(null, new[] { epConfig }, new[] { contractType }, true, out ep);
                    break;
                }
            }

            if (ep == null)
            {
                throw new InvalidOperationException(ExceptionMessages.HostingConfig_NoContext);
            }

            return (ChannelFactory)Activator.CreateInstance(typeof(ChannelFactory<>).MakeGenericType(contractType), ep);
        }

        Uri GetServiceAddress(Type contractType, int? epPort, ProtocolConfigurationDescription protocolConfig, bool searchInBaseAddresses)
        {
            return GetServiceAddress(contractType, epPort, protocolConfig, searchInBaseAddresses, null, null);
        }

        Uri GetServiceAddress(Type contractType, int? epPort, ProtocolConfigurationDescription protocolConfig, bool searchInBaseAddresses, string suffix, string epSuffix)
        {
            string address = string.Empty;

            if (searchInBaseAddresses)
            {
                Uri addressUriMatch = BaseAddresses.Where(a => a.Scheme == protocolConfig.ProtocolDescription.Scheme).FirstOrDefault();

                address = protocolConfig.ProtocolDescription.HandleBaseAddress(addressUriMatch.AbsoluteUri);

                if (!string.IsNullOrEmpty(suffix))
                {
                    address = EndpointHelper.CombineAddresses(address, suffix);
                }

                address = address.Replace("{machine}", GetMachineName(contractType, protocolConfig.ProtocolDescription));
            }
            else
            {
                int port = epPort.HasValue ? epPort.Value : protocolConfig.Port;

                string format = port > 0 ? "{0}://{1}:{2}" : "{0}://{1}";

                address = string.Format(format,
                    protocolConfig.ProtocolDescription.Scheme, GetMachineName(contractType, protocolConfig.ProtocolDescription), port);

                address = protocolConfig.ProtocolDescription.HandleBaseAddress(address);

                address = EndpointHelper.CombineAddresses(address, ServiceAddress);
                if (!string.IsNullOrEmpty(suffix))
                {
                    address = EndpointHelper.CombineAddresses(address, suffix);
                }
            }

            if (!string.IsNullOrEmpty(epSuffix))
            {
                address = EndpointHelper.CombineAddresses(address, epSuffix);
            }

            return new Uri(address);
        }

        System.ServiceModel.Channels.Binding GetBinding(EndpointConfigurationDescription endpointConfig, out IEndpointBehavior[] applyBehaviors)
        {
            applyBehaviors = null;

            System.ServiceModel.Channels.Binding b = null;

            bool noSecurity = endpointConfig.SecurityMode == KnownSecurityMode.None;

            switch (endpointConfig.KnownConfiguration)
            {
                case KnownEndpointConfiguration.NetTcp:
                    #region NetTcp
                    b = new NetTcpBinding();

                    ((NetTcpBinding)b).ReliableSession.InactivityTimeout = InactivityTimeout;

                    if (endpointConfig.TransactionFlow.HasValue)
                    {
                        ((NetTcpBinding)b).TransactionFlow = endpointConfig.TransactionFlow.Value;
                    }

                    if (noSecurity)
                    {
                        ((NetTcpBinding)b).Security.Mode = SecurityMode.None;
                    }
                    #endregion

                    break;

                case KnownEndpointConfiguration.NetTcpReliableSession:
                    #region NetTcpReliableSession
                    b = new NetTcpBinding();

                    ((NetTcpBinding)b).ReliableSession.Enabled = true;
                    ((NetTcpBinding)b).ReliableSession.InactivityTimeout = InactivityTimeout;

                    if (endpointConfig.TransactionFlow.HasValue)
                    {
                        ((NetTcpBinding)b).TransactionFlow = endpointConfig.TransactionFlow.Value;
                    }

                    if (noSecurity)
                    {
                        ((NetTcpBinding)b).Security.Mode = SecurityMode.None;
                    }
                    #endregion

                    break;

                case KnownEndpointConfiguration.NetTcpOneWay:
                    #region NetTcpOneWay
                    BinaryMessageEncodingBindingElement encodingElem = new BinaryMessageEncodingBindingElement();
                    BindingHelper.SetReaderQuotasValues(encodingElem.ReaderQuotas, int.MaxValue);

                    List<BindingElement> elems = new List<BindingElement>
                    {
                        new OneWayBindingElement()
                    };

                    if (!noSecurity)
                    {
                        elems.Add(new WindowsStreamSecurityBindingElement());
                    }

                    elems.Add(encodingElem);
                    elems.Add(new TcpTransportBindingElement { MaxBufferPoolSize = int.MaxValue, MaxBufferSize = int.MaxValue, MaxReceivedMessageSize = int.MaxValue });

                    b = new CustomBinding(elems.ToArray());
                    #endregion

                    break;

                case KnownEndpointConfiguration.BasicHttp:
                    #region BasicHttpBinding
                    b = new BasicHttpBinding();

                    if (noSecurity)
                    {
                        ((BasicHttpBinding)b).Security.Mode = BasicHttpSecurityMode.None;
                    }
                    else
                    {
                        ((BasicHttpBinding)b).Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
                        ((BasicHttpBinding)b).Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
                    }
                    #endregion

                    break;

                case KnownEndpointConfiguration.HttpBinary:
                    #region HttpBinary
                    BinaryMessageEncodingBindingElement binaryElem = new BinaryMessageEncodingBindingElement();
                    BindingHelper.SetReaderQuotasValues(binaryElem.ReaderQuotas, int.MaxValue);

                    BindingElement[] bindingElems =
                    {
                        binaryElem,
                        new HttpTransportBindingElement
                        {
                            MaxBufferPoolSize = int.MaxValue,
                            MaxBufferSize = int.MaxValue,
                            MaxReceivedMessageSize = int.MaxValue,
                            AuthenticationScheme = noSecurity ? AuthenticationSchemes.Anonymous : AuthenticationSchemes.Negotiate
                        }
                    };

                    b = new CustomBinding(bindingElems);
                    #endregion

                    break;

                case KnownEndpointConfiguration.WsHttp:
                    #region WsHttp
                    b = new WSHttpBinding();

                    ((WSHttpBinding)b).ReliableSession.InactivityTimeout = InactivityTimeout;

                    if (noSecurity)
                    {
                        ((WSHttpBinding)b).Security.Mode = SecurityMode.None;
                    }

                    if (endpointConfig.TransactionFlow.HasValue)
                    {
                        ((WSHttpBinding)b).TransactionFlow = endpointConfig.TransactionFlow.Value;
                    }
                    #endregion

                    break;

                case KnownEndpointConfiguration.NetNamedPipe:
                    #region NetNamedPipe
                    b = new NetNamedPipeBinding();

                    if (noSecurity)
                    {
                        ((NetNamedPipeBinding)b).Security.Mode = NetNamedPipeSecurityMode.None;
                    }

                    if (endpointConfig.TransactionFlow.HasValue)
                    {
                        ((NetNamedPipeBinding)b).TransactionFlow = endpointConfig.TransactionFlow.Value;
                    }
                    #endregion

                    break;

                case KnownEndpointConfiguration.NetMsmq:
                    #region NetMsmq
                    MsmqEndpointConfigurationDescription desc = (MsmqEndpointConfigurationDescription)endpointConfig;

                    b = new NetMsmqBinding()
                    {
                        Durable = desc.Details.Durable,
                        ExactlyOnce = desc.Details.ExactlyOnce,
                        TimeToLive = desc.Details.TimeToLive,
                        ReceiveErrorHandling = desc.Details.ReceiveErrorHandling,
                        ReceiveRetryCount = desc.Details.ReceiveRetryCount,
                        MaxRetryCycles = desc.Details.MaxRetryCycles,
                        RetryCycleDelay = desc.Details.RetryCycleDelay
                    };

                    if (noSecurity)
                    {
                        ((NetMsmqBinding)b).Security.Mode = NetMsmqSecurityMode.None;
                    }

                    if (endpointConfig.TransactionFlow.HasValue)
                    {
                        ((NetNamedPipeBinding)b).TransactionFlow = endpointConfig.TransactionFlow.Value;
                    }
                    #endregion

                    break;

                case KnownEndpointConfiguration.WebHttp:
                case KnownEndpointConfiguration.WebHttpScriptEnabled:
                    #region WebHttp / WebHttpScriptEnabled
                    b = new WebHttpBinding();

                    if (noSecurity)
                    {
                        ((WebHttpBinding)b).Security.Mode = WebHttpSecurityMode.None;
                    }
                    else
                    {
                        ((WebHttpBinding)b).Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
                        ((WebHttpBinding)b).Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
                    }

                    if (endpointConfig.KnownConfiguration == KnownEndpointConfiguration.WebHttpScriptEnabled)
                    {
                        applyBehaviors = new IEndpointBehavior[]
                        {
                            new WebScriptEnablingBehavior()
                        };
                    }
                    else
                    {
                        applyBehaviors = new IEndpointBehavior[]
                        {
                            new WebHttpBehavior()
                        };
                    }
                    #endregion

                    break;
            }

            b.SendTimeout = SendTimeout;
            b.ReceiveTimeout = ReceiveTimeout;
            b.OpenTimeout = OpenTimeout;
            b.CloseTimeout = CloseTimeout;

            return b;
        }
        #endregion

		#region Helpers
        string GetMachineName(Type contractType, ProtocolDescription protocolDescription)
        {
            string machineName = null;

            if (protocolDescription.UseLocalAddress)
            {
                machineName = LocalMachineAddress;
            }
            else
            {
                machineName = string.IsNullOrEmpty(MachineAddress)
                        ? ConsoleHelper.HasConsoleWindow() ? LocalMachineAddress : Environment.MachineName
                        : MachineAddress;
            }

            if (protocolDescription.Protocol != Protocol.Http && machineName != LocalMachineAddress)
            {
                machineName += FullMachineNameAggregate;
            }

            return machineName;
        }

		ProtocolConfigurationDescription GetProtocolDescription(Protocol protocol)
		{
			return Protocols.Where(p => p.ProtocolDescription.Protocol == protocol).FirstOrDefault();
		}

		internal bool HasMetadataOption(MetadataOptions option)
		{
			return ((option & Metadata) == option);
		}

		bool AddProtocolIfNotPresent(ProtocolConfigurationDescription protocolConfiguration)
		{
			if (!HasProtocol(protocolConfiguration.ProtocolDescription.Protocol))
			{
				Protocols.Add(protocolConfiguration);
				return true;
			}

			return false;
		}

		bool HasProtocol(Protocol protocol)
		{
			return Protocols.Where(p => p.ProtocolDescription.Protocol == protocol).FirstOrDefault() != null;
		}
		#endregion

		#region API
        #region Configuration Creation and Handling
        /// <summary>
        /// Creates the configuratiion with the specified service address suffix.
        /// </summary>
        /// <param name="serviceAddressSuffix">The service address suffix.</param>
        /// <returns></returns>
        public static ServiceConfigurationDescription Create(string serviceAddressSuffix)
        {
            return new ServiceConfigurationDescription
            {
                ServiceAddress = serviceAddressSuffix
            };
        }

        /// <summary>
        /// Creates the configuratiion with the specified base addresses.
        /// </summary>
        /// <param name="baseAddresses">The base addresses.</param>
        /// <returns></returns>
        public static ServiceConfigurationDescription Create(params string[] baseAddresses)
        {
            return Create(baseAddresses.Select(a => new Uri(a)).ToArray());
        }

        /// <summary>
        /// Creates the configuratiion with the specified base addresses.
        /// </summary>
        /// <param name="baseAddresses">The base addresses.</param>
        /// <returns></returns>
        public static ServiceConfigurationDescription Create(params Uri[] baseAddresses)
        {
            return new ServiceConfigurationDescription
            {
                BaseAddresses = baseAddresses
            };
        }

        /// <summary>
        /// Applies the specified action.
        /// </summary>
        /// <param name="action">The action.</param>
        /// <returns></returns>
        public ServiceConfigurationDescription Apply(Action<ServiceConfigurationDescription> action)
        {
            action(this);

            return this;
        }
        #endregion

        #region Create Default
        /// <summary>
        /// Creates the most default configurations.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="desiredNetTcpEndpoint">The desired net TCP endpoint.</param>
        /// <param name="baseAddresses">The base addresses.</param>
        /// <returns>The service configuration</returns>
        public static ServiceConfigurationDescription CreateDefault(int? netTcpPort, int? httpPort, KnownEndpointConfiguration? desiredNetTcpEndpoint, params string[] baseAddresses)
        {
            return CreateDefault(netTcpPort, httpPort, desiredNetTcpEndpoint, baseAddresses.Select(a => new Uri(a)).ToArray(), null);
        }

        /// <summary>
        /// Creates the most default configurations.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="desiredNetTcpEndpoint">The desired net TCP endpoint.</param>
        /// <param name="baseAddresses">The base addresses.</param>
        /// <returns>The service configuration</returns>
        public static ServiceConfigurationDescription CreateDefault(int? netTcpPort, int? httpPort, KnownEndpointConfiguration? desiredNetTcpEndpoint, params Uri[] baseAddresses)
        {
            return CreateDefault(netTcpPort, httpPort, desiredNetTcpEndpoint, baseAddresses, null);
        }

        /// <summary>
        /// Creates the most default configurations.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="desiredNetTcpEndpoint">The desired net TCP endpoint.</param>
        /// <param name="serviceAddressSuffix">The service address suffix.</param>
        /// <returns>The service configuration</returns>
        public static ServiceConfigurationDescription CreateDefault(int? netTcpPort, int? httpPort, KnownEndpointConfiguration? desiredNetTcpEndpoint, string serviceAddressSuffix)
        {
            return CreateDefault(netTcpPort, httpPort, desiredNetTcpEndpoint, (Uri[])null, serviceAddressSuffix);
        }

        static ServiceConfigurationDescription CreateDefault(int? netTcpPort, int? httpPort, KnownEndpointConfiguration? desiredNetTcpEndpoint, Uri[] baseAddresses, string serviceAddressSuffix)
        {
            ServiceConfigurationDescription config =
                ServiceConfigurationDescription.Create(serviceAddressSuffix)
                    .Apply(c => c.BaseAddresses = baseAddresses);

            if (httpPort.HasValue)
            {
                config.WithBasicHttp(httpPort.Value);
            }
            if (netTcpPort.HasValue)
            {
                config.WithNetTcp(netTcpPort.Value,
                    desiredNetTcpEndpoint.HasValue ? desiredNetTcpEndpoint.Value : KnownEndpointConfiguration.NetTcp);
            }

            config.MakeDiscoverable();

            return config;
        }
        #endregion

        #region Endpoints
        #region BasicHttp
        /// <summary>
        /// Adds a basic-http endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithBasicHttp(string suffix, params Type[] contractTypes)
        {
            return WithBasicHttp(suffix, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a basic-http endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithBasicHttp(string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithBasicHttp(null, suffix, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a basic-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithBasicHttp(int httpPort, params Type[] contractTypes)
        {
            return WithBasicHttp(httpPort, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a basic-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithBasicHttp(int httpPort, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithBasicHttp(httpPort, null, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a basic-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithBasicHttp(int? httpPort, string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return With(httpPort, null, suffix, ProtocolDescription.Http,
                KnownEndpointConfiguration.BasicHttp, securityMode, null, contractTypes);
        }
        #endregion

        #region HttpBinary
        /// <summary>
        /// Adds a http transport with binary message encoding endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithHttpBinary(string suffix, params Type[] contractTypes)
        {
            return WithHttpBinary(suffix, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a http transport with binary message encoding endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithHttpBinary(string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithHttpBinary(null, suffix, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a http transport with binary message encoding endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithHttpBinary(int httpPort, params Type[] contractTypes)
        {
            return WithHttpBinary(httpPort, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a http transport with binary message encoding endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithHttpBinary(int httpPort, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithHttpBinary(httpPort, null, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a http transport with binary message encoding endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithHttpBinary(int? httpPort, string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return With(httpPort, null, suffix, ProtocolDescription.Http,
                KnownEndpointConfiguration.HttpBinary, securityMode, null, contractTypes);
        }
        #endregion

        #region WsHttp
        /// <summary>
        /// Adds a ws-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWsHttp(int httpPort, params Type[] contractTypes)
        {
            return WithWsHttp(httpPort, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a ws-http endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWsHttp(string suffix, params Type[] contractTypes)
        {
            return WithWsHttp(suffix, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a ws-http endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWsHttp(string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithWsHttp(null, suffix, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a ws-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWsHttp(int httpPort, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithWsHttp(httpPort, null, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a ws-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWsHttp(int? httpPort, string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithWsHttp(httpPort, null, suffix, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a ws-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWsHttp(int? httpPort, bool? transactionFlow, string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return With(httpPort, transactionFlow, suffix, ProtocolDescription.Http,
                KnownEndpointConfiguration.WsHttp, securityMode, null, contractTypes);
        }
        #endregion

        #region NetTcp
        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(KnownEndpointConfiguration netTcpConfiguration, params Type[] contractTypes)
        {
            return WithNetTcp(0, netTcpConfiguration, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(int netTcpPort, KnownEndpointConfiguration netTcpConfiguration, params Type[] contractTypes)
        {
            return WithNetTcp(netTcpPort, netTcpConfiguration, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(int netTcpPort, KnownEndpointConfiguration netTcpConfiguration, bool transactionFlow, params Type[] contractTypes)
        {
            return WithNetTcp(netTcpPort, netTcpConfiguration, DefaultSecurityMode, transactionFlow, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(int netTcpPort, KnownEndpointConfiguration netTcpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithNetTcp(netTcpPort, null, netTcpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(int netTcpPort, KnownEndpointConfiguration netTcpConfiguration, KnownSecurityMode securityMode, bool transactionFlow, params Type[] contractTypes)
        {
            return WithNetTcp(netTcpPort, transactionFlow, null, netTcpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(string suffix, KnownEndpointConfiguration netTcpConfiguration, params Type[] contractTypes)
        {
            return WithNetTcp(suffix, netTcpConfiguration, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(string suffix, KnownEndpointConfiguration netTcpConfiguration, bool transactionFlow, params Type[] contractTypes)
        {
            return WithNetTcp(suffix, netTcpConfiguration, DefaultSecurityMode, transactionFlow, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(string suffix, KnownEndpointConfiguration netTcpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithNetTcp(null, suffix, netTcpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(string suffix, KnownEndpointConfiguration netTcpConfiguration, KnownSecurityMode securityMode, bool transactionFlow, params Type[] contractTypes)
        {
            return WithNetTcp(null, transactionFlow, suffix, netTcpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(int? netTcpPort, string suffix, KnownEndpointConfiguration netTcpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithNetTcp(netTcpPort, null, suffix, netTcpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a net-tcp endpoint to the configuration.
        /// </summary>
        /// <param name="netTcpPort">The net TCP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="netTcpConfiguration">The net TCP endpoint configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetTcp(int? netTcpPort, bool? transactionFlow, string suffix, KnownEndpointConfiguration netTcpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return With(netTcpPort, transactionFlow, suffix, ProtocolDescription.NetTcp, netTcpConfiguration, securityMode, null, contractTypes);
        }
        #endregion

        #region NetNamedPipe
        /// <summary>
        /// Adds a named-pipe endpoint to the configuration.
        /// </summary>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetNamedPipe(params Type[] contractTypes)
        {
            return WithNetNamedPipe(DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a named-pipe endpoint to the configuration.
        /// </summary>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetNamedPipe(KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithNetNamedPipe(null, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a named-pipe endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetNamedPipe(string suffix, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithNetNamedPipe(suffix, securityMode, null, contractTypes);
        }

        /// <summary>
        /// Adds a named-pipe endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="transactionFlow">Whether or not to enabile transaction flow.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetNamedPipe(string suffix, KnownSecurityMode securityMode, bool? transactionFlow, params Type[] contractTypes)
        {
            return With(0, transactionFlow, suffix, ProtocolDescription.NetNamedPipe,
                KnownEndpointConfiguration.NetNamedPipe, securityMode, null, contractTypes);
        }
        #endregion

        #region NetMsmq
        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(params Type[] contractTypes)
        {
            return WithNetMsmq(null, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="detailsAction">The details action.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(Action<MsmqEndpointDetails> detailsAction, params Type[] contractTypes)
        {
            MsmqEndpointDetails details = new MsmqEndpointDetails();

            if (detailsAction != null)
            {
                detailsAction(details);
            }

            return WithNetMsmq(DefaultSecurityMode, details, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="resembleMsmq3">if set to <c>true</c> the binding will resemble msmq v3.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(bool resembleMsmq3, params Type[] contractTypes)
        {
            return WithNetMsmq(resembleMsmq3, null, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="resembleMsmq3">if set to <c>true</c> the binding will resemble msmq v3.</param>
        /// <param name="detailsAction">The details action.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(bool resembleMsmq3, Action<MsmqEndpointDetails> detailsAction, params Type[] contractTypes)
        {
            return WithNetMsmq(DefaultSecurityMode, resembleMsmq3, detailsAction, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="resembleMsmq3">if set to <c>true</c> the binding will resemble msmq v3.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(KnownSecurityMode securityMode, bool resembleMsmq3, params Type[] contractTypes)
        {
            return WithNetMsmq(securityMode, resembleMsmq3, null, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="resembleMsmq3">if set to <c>true</c> the binding will resemble msmq v3.</param>
        /// <param name="detailsAction">The details action.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(KnownSecurityMode securityMode, bool resembleMsmq3, Action<MsmqEndpointDetails> detailsAction, params Type[] contractTypes)
        {
            return WithNetMsmq(null, securityMode, resembleMsmq3, detailsAction, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="resembleMsmq3">if set to <c>true</c> the binding will resemble msmq v3.</param>
        /// <param name="applyDetails">The apply details action.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns></returns>
        public ServiceConfigurationDescription WithNetMsmq(string suffix, KnownSecurityMode securityMode, bool resembleMsmq3, Action<MsmqEndpointDetails> applyDetails, params Type[] contractTypes)
        {
            MsmqEndpointDetails details = new MsmqEndpointDetails();

            if (resembleMsmq3)
            {
                details.MaxRetryCycles = 0;
                details.RetryCycleDelay = TimeSpan.Zero;
            }

            if (applyDetails != null)
            {
                applyDetails(details);
            }

            return WithNetMsmq(suffix, securityMode, details, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="msmqDetails">The MSMQ endpoint details.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(KnownSecurityMode securityMode, MsmqEndpointDetails msmqDetails, params Type[] contractTypes)
        {
            return WithNetMsmq(null, securityMode, msmqDetails, contractTypes);
        }

        /// <summary>
        /// Adds a net-msmq endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="msmqDetails">The MSMQ endpoint details.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithNetMsmq(string suffix, KnownSecurityMode securityMode, MsmqEndpointDetails msmqDetails, params Type[] contractTypes)
        {
            return With(0, null, suffix, ProtocolDescription.NetMsmq,
                KnownEndpointConfiguration.NetMsmq, securityMode,
                () => new MsmqEndpointConfigurationDescription(msmqDetails), contractTypes);
        }
        #endregion

        #region WebHttp
        /// <summary>
        /// Adds a web-http endpoint to the configuration
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="webHttpConfiguration">The web HTTP configuration.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWebHttp(string suffix, KnownEndpointConfiguration webHttpConfiguration, params Type[] contractTypes)
        {
            return WithWebHttp(suffix, webHttpConfiguration, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a web-http endpoint to the configuration.
        /// </summary>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="webHttpConfiguration">The web HTTP configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWebHttp(string suffix, KnownEndpointConfiguration webHttpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithWebHttp(null, suffix, webHttpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a web-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="webHttpConfiguration">The web HTTP configuration.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWebHttp(int httpPort, KnownEndpointConfiguration webHttpConfiguration, params Type[] contractTypes)
        {
            return WithWebHttp(httpPort, webHttpConfiguration, DefaultSecurityMode, contractTypes);
        }

        /// <summary>
        /// Adds a web-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="webHttpConfiguration">The web HTTP configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWebHttp(int httpPort, KnownEndpointConfiguration webHttpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return WithWebHttp(httpPort, null, webHttpConfiguration, securityMode, contractTypes);
        }

        /// <summary>
        /// Adds a web-http endpoint to the configuration.
        /// </summary>
        /// <param name="httpPort">The HTTP port.</param>
        /// <param name="suffix">The service address suffix.</param>
        /// <param name="webHttpConfiguration">The web HTTP configuration.</param>
        /// <param name="securityMode">The security mode.</param>
        /// <param name="contractTypes">The specific supported contracts</param>
        /// <returns>The service configuration</returns>
        public ServiceConfigurationDescription WithWebHttp(int? httpPort, string suffix, KnownEndpointConfiguration webHttpConfiguration, KnownSecurityMode securityMode, params Type[] contractTypes)
        {
            return With(httpPort, null, suffix, ProtocolDescription.Http, webHttpConfiguration, securityMode, null, contractTypes);
        }
        #endregion

        internal ServiceConfigurationDescription With(int? port, bool? transactionFlow, string suffix,
            ProtocolDescription protocol, KnownEndpointConfiguration configuration, KnownSecurityMode securityMode,
            Func<EndpointConfigurationDescription> configurationResolver, Type[] contractTypes)
        {
            bool setSuffix = !port.HasValue;
            if (port.HasValue)
            {
                if (!AddProtocolIfNotPresent(new ProtocolConfigurationDescription(protocol, port.Value)))
                {
                    setSuffix = true;
                }
            }
            else
            {
                AddProtocolIfNotPresent(new ProtocolConfigurationDescription(protocol, 0));
            }

            EndpointConfigurationDescription config = configurationResolver == null
                ? new EndpointConfigurationDescription()
                : configurationResolver();

            config.TransactionFlow = transactionFlow;
            config.Port = port;
            config.Protocol = protocol.Protocol;
            config.SecurityMode = securityMode;
            config.KnownConfiguration = configuration;
            config.ContractTypes = contractTypes;

            if (setSuffix)
            {
                config.AddressSuffix = suffix;
            }

            Endpoints.Add(config);

            return this;
        }
        #endregion

        #region Metadata
        /// <summary>
        /// Makes the service discoverable.
        /// </summary>
        /// <returns></returns>
        public ServiceConfigurationDescription MakeDiscoverable()
        {
            bool http = Protocols.Where(p => p.ProtocolDescription.Protocol == Protocol.Http).FirstOrDefault() != null;

            if (http)
            {
                return MakeDiscoverable(MetadataOptions.HttpGet);
            }
            else
            {
                return MakeDiscoverable(MetadataOptions.MetadataExchangeTcp);
            }
        }

        /// <summary>
        /// Makes the service discoverable.
        /// </summary>
        /// <param name="metadataOptions">The metadata options.</param>
        /// <returns></returns>
        public ServiceConfigurationDescription MakeDiscoverable(MetadataOptions metadataOptions)
        {
            Metadata = metadataOptions;

            return this;
        }
        #endregion

        #region Service Host
        /// <summary>
        /// Generates the service host.
        /// </summary>
        /// <typeparam name="T">The type of the service implementation</typeparam>
        /// <returns>The service host</returns>
        public ServiceHost<T> GenerateServiceHost<T>()
        {
            return GenerateServiceHost<T>(null);
        }

        /// <summary>
        /// Generates the service host.
        /// </summary>
        /// <typeparam name="T">The type of the service implementation.</typeparam>
        /// <param name="hostAction">The host action.</param>
        /// <returns>The service host</returns>
        public ServiceHost<T> GenerateServiceHost<T>(Action<ServiceHost<T>> hostAction)
        {
            var host = new ServiceHost<T>(this);

            if (hostAction != null)
            {
                hostAction(host);
            }

            return host;
        }
        #endregion
		#endregion
	}
}
